//
// Created by gastonfeng on 2022/9/20.
//

#ifndef BOOTLOADER_STM32_NSPI_H
#define BOOTLOADER_STM32_NSPI_H

#include <cstdint>

#ifdef ARDUINO

#include <Arduino.h>
#include <SPI.h>

#endif
#ifdef ARDUINO_ARCH_GD32

#include <gd32_def.h>

#endif
#ifdef STM32F407xx

#include "stm32f4xx_ll_gpio.h"
#include "stm32f4xx_ll_spi.h"

#endif
#if defined(STM32H750xx)

#include <stm32h7xx_ll_gpio.h>
#include <stm32h7xx_ll_spi.h>
#endif
#ifdef STM32F103xx
#include "stm32f1xx_ll_gpio.h"
#include "stm32f1xx_ll_spi.h"
#endif

#define SPI_HAS_TRANSACTION 1

// Compatibility with sketches designed for AVR @ 16 MHz could not
// be ensured as SPI frequency depends of system clock configuration.
// user have to use appropriate divider for the SPI clock
// This function should not be used in new project.
// Use SPISettings with SPI.beginTransaction() to configure SPI parameters.
#define SPI_CLOCK_DIV2 2
#define SPI_CLOCK_DIV4 4
#define SPI_CLOCK_DIV8 8
#define SPI_CLOCK_DIV16 16
#define SPI_CLOCK_DIV32 32
#define SPI_CLOCK_DIV64 64
#define SPI_CLOCK_DIV128 128

// SPI mode parameters for SPISettings
#define SPI_MODE0 0x00
#define SPI_MODE1 0x01
#define SPI_MODE2 0x02
#define SPI_MODE3 0x03

#define SPI_TRANSMITRECEIVE 0x0
#define SPI_TRANSMITONLY 0x1
#ifndef ARDUINO_ARCH_STM32

#define SPI_SPEED_CLOCK_DEFAULT     40000000
typedef enum {
    SPI_MODE_0 = 0x00,
    SPI_MODE_1 = 0x01,
    SPI_MODE_2 = 0x02,
    SPI_MODE_3 = 0x03
} spi_mode_e;

// Transfer mode
enum SPITransferMode {
    SPI_CONTINUE, /* Transfer not finished: CS pin kept active */
    SPI_LAST      /* Transfer ended: CS pin released */
};
// Indicates the user controls himself the CS pin outside of the spi class
#define CS_PIN_CONTROLLED_BY_USER -1

// Indicates there is no configuration selected
#define NO_CONFIG ((int16_t)(-1))

// Defines a default timeout delay in milliseconds for the SPI transfer
#ifndef SPI_TRANSFER_TIMEOUT
#define SPI_TRANSFER_TIMEOUT 1000
#endif

/*
 * Defines the number of settings saved per SPI instance. Must be in range 1 to 254.
 * Can be redefined in variant.h
 */
#ifndef NB_SPI_SETTINGS
#define NB_SPI_SETTINGS 4
#endif
#ifndef ARDUINO
class SPISettings {
public:
    SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode, bool noRecv = SPI_TRANSMITRECEIVE) {
        clk = clock;
        bOrder = bitOrder;
        noReceive = noRecv;

        if (SPI_MODE0 == dataMode) {
            dMode = SPI_MODE_0;
        } else if (SPI_MODE1 == dataMode) {
            dMode = SPI_MODE_1;
        } else if (SPI_MODE2 == dataMode) {
            dMode = SPI_MODE_2;
        } else if (SPI_MODE3 == dataMode) {
            dMode = SPI_MODE_3;
        }
    }

    SPISettings() {
        pinCS = -1;
        clk = SPI_SPEED_CLOCK_DEFAULT;
        bOrder = MSBFIRST;
        dMode = SPI_MODE_0;
    }

private:
    int16_t pinCS;    // CS pin associated to the configuration
    uint32_t clk;     // specifies the spi bus maximum clock speed
    BitOrder bOrder;  // bit order (MSBFirst or LSBFirst)
    spi_mode_e dMode; // one of the data mode
    // Mode          Clock Polarity (CPOL)   Clock Phase (CPHA)
    // SPI_MODE0             0                     0
    // SPI_MODE1             0                     1
    // SPI_MODE2             1                     0
    // SPI_MODE3             1                     1
    friend class SPIClass;

    bool noReceive;
};
#endif
#endif
typedef enum {
    GET_IDX = 0,
    ADD_NEW_PIN = 1
} pin_option_t;

class nSPI {
public:
    nSPI(PinName mosi, PinName miso, PinName sclk, PinName ssel = NC);

    // setMISO/MOSI/SCLK/SSEL have to be called before begin()
    //    void setMISO(uint32_t miso)
    //    {
    //      _spi.pin_miso = digitalPinToPinName(miso);
    //    };
    //    void setMOSI(uint32_t mosi)
    //    {
    //      _spi.pin_mosi = digitalPinToPinName(mosi);
    //    };
    //    void setSCLK(uint32_t sclk)
    //    {
    //      _spi.pin_sclk = digitalPinToPinName(sclk);
    //    };
    //    void setSSEL(uint32_t ssel)
    //    {
    //      _spi.pin_ssel = digitalPinToPinName(ssel);
    //    };
#ifdef ARDUINO_ARCH_STM32

    nSPI(SPI_TypeDef *pDef);

    nSPI();

#endif

    virtual void begin(PinName _pin = NC);

    void end(void);

    /* This function should be used to configure the SPI instance in case you
     * don't use default parameters.
     * You can attach another CS pin to the SPI instance and each CS pin can be
     * attach with specific SPI settings.
     */
    virtual void beginTransaction(PinName pin, SPISettings settings);

    virtual void beginTransaction(const SPISettings settings) {
        beginTransaction(NC, settings);
    }

    virtual void endTransaction(PinName pin);

    virtual void endTransaction(void) {
        endTransaction(NC);
    }

    /* Transfer functions: must be called after initialization of the SPI
     * instance with begin() or beginTransaction().
     * You can specify the CS pin to use.
     */
    virtual uint8_t transfer(PinName pin, uint8_t _data, SPITransferMode _mode = SPI_LAST);

    uint16_t transfer16(PinName pin, uint16_t _data, SPITransferMode _mode = SPI_LAST);

    virtual void transfer(PinName pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST);

    int transfer(PinName _pin, uint8_t *_bufout, uint8_t *_bufin, size_t _count, SPITransferMode _mode = SPI_LAST);

    // Transfer functions when user controls himself the CS pin.
    virtual uint8_t transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST) {
        return transfer(NC, _data, _mode);
    }

    uint16_t transfer16(uint16_t _data, SPITransferMode _mode = SPI_LAST) {
        return transfer16(NC, _data, _mode);
    }

    virtual void transfer(uint8_t *_buf, size_t _count, SPITransferMode _mode = SPI_LAST) {
        transfer(NC, _buf, _count, _mode);
    }

    virtual void transfer(uint8_t *_bufout, uint8_t *_bufin, size_t _count, SPITransferMode _mode = SPI_LAST) {
        transfer(NC, _bufout, _bufin, _count, _mode);
    }

    /* These methods are deprecated and kept for compatibility.
     * Use SPISettings with SPI.beginTransaction() to configure SPI parameters.
     */
    //    void setBitOrder(uint8_t _pin, BitOrder);
    //    void setBitOrder(BitOrder _order)
    //    {
    //      setBitOrder(CS_PIN_CONTROLLED_BY_USER, _order);
    //    }
    //
    //    void setDataMode(uint8_t _pin, uint8_t);
    //    void setDataMode(uint8_t _mode)
    //    {
    //      setDataMode(CS_PIN_CONTROLLED_BY_USER, _mode);
    //    }

    //    void setClockDivider(uint8_t _pin, uint8_t);
    //    void setClockDivider(uint8_t _div)
    //    {
    //      setClockDivider(CS_PIN_CONTROLLED_BY_USER, _div);
    //    }

private:
    spi_t _spi;
};

#endif // BOOTLOADER_STM32_NSPI_H
